﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Xml;
using User.Api.Helpers.WeixinPayV3.WxResult;

namespace User.Api.Helpers.WeixinPayV3
{
    public class RefundQueryHelper
    {
        private const string GateUrl = "https://gw.tenpay.com/gateway/normalrefundquery.xml";
      
        private readonly Hashtable _requestParameters;
        private readonly Hashtable _responseParameters;
        private string _appid;
        private string _appkey;
        private string _mchid;
        private string _certfile;
        private string _certfilepwd;
        public RefundQueryHelper(string appid, string appkey, string mchid, string certfile, string certfilepwd)
        {
            _appid = appid;
            _appkey = appkey;
            _mchid = mchid;
            _certfile = certfile;
            _certfilepwd = certfilepwd;
            _requestParameters = new Hashtable();
            _responseParameters = new Hashtable();
        }

        private void SetRequestParameter(string key, string value)
        {
            if (!string.IsNullOrEmpty(key))
            {
                if (_requestParameters.Contains(key))
                {
                    _requestParameters.Remove(key);
                }

                _requestParameters.Add(key, value);
            }
        }

        private void CreateSign()
        {
            var sb = new StringBuilder();

            var akeys = new ArrayList(_requestParameters.Keys);
            akeys.Sort();

            foreach (string k in akeys)
            {
                var v = (string)_requestParameters[k];
                if (null != v && "".CompareTo(v) != 0
                    && "sign".CompareTo(k) != 0 && "key".CompareTo(k) != 0)
                {
                    sb.Append(k + "=" + v + "&");
                }
            }

            sb.Append("key=" + _appkey);
            string sign = MD5Util.GetMd5(sb.ToString());

            SetRequestParameter("sign", sign);
        }

        private string GetRequestUrl()
        {
            CreateSign();

            var sb = new StringBuilder();
            var akeys = new ArrayList(_requestParameters.Keys);
            akeys.Sort();
            foreach (string k in akeys)
            {
                string v = (string)_requestParameters[k];
                if (null != v && "key".CompareTo(k) != 0 && "spbill_create_ip".CompareTo(k) != 0)
                {
                    sb.Append(k + "=" + WebUtility.UrlEncode(v) + "&");
                }
                else if ("spbill_create_ip".CompareTo(k) == 0)
                {
                    sb.Append(k + "=" + v.Replace(".", "%2E") + "&");
                }
            }

            //去掉最后一个&
            if (sb.Length > 0)
            {
                sb.Remove(sb.Length - 1, 1);
            }

            return GateUrl + "?" + sb.ToString();
        }

        private void SetResponseParameter(string key, string value)
        {
            if (!string.IsNullOrEmpty(key))
            {
                if (_responseParameters.Contains(key))
                {
                    _responseParameters.Remove(key);
                }

                _responseParameters.Add(key, value);
            }
        }

        private void SetAllResponseParameters(string content)
        {
            var xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(content);
            var root = xmlDoc.SelectSingleNode("root");
            var xnl = root.ChildNodes;

            foreach (XmlNode xnf in xnl)
            {
                this.SetResponseParameter(xnf.Name, xnf.InnerXml);
            }
        }

        private string GetResponseParameter(string key)
        {
            var s = (string)_responseParameters[key];
            return s ?? "";
        }

        private bool VerifySign()
        {
            var sb = new StringBuilder();

            var akeys = new ArrayList(_responseParameters.Keys);
            akeys.Sort();

            foreach (string k in akeys)
            {
                string v = (string)_responseParameters[k];
                if (null != v && "".CompareTo(v) != 0
                    && "sign".CompareTo(k) != 0 && "key".CompareTo(k) != 0)
                {
                    sb.Append(k + "=" + v + "&");
                }
            }

            sb.Append("key=" + _appkey);
            string sign = MD5Util.GetMd5(sb.ToString()).ToLower();

            return GetResponseParameter("sign").ToLower().Equals(sign);
        }



        /// <summary>
        /// 执行微信退款查询操作
        /// </summary>
        /// <param name="param"></param>
        /// <param name="result"></param>
        /// <returns>1-通讯成功退款查询成功；2-通讯失败或验签失败</returns>
        public int DoRefundQuery(RefundQueryParam param, out RefundQueryResult result)
        {
            result = new RefundQueryResult();

            SetRequestParameter("partner",_mchid);
            //out_trade_no 和 transaction_id、out_refund_no、refund_id 至少一个必填，同时存在时以优先级高为准，优先级为：
            //refund_id > out_refund_no > transaction_id > out_trade_no
            SetRequestParameter("out_trade_no", param.OutTradeNo);
            SetRequestParameter("transaction_id", param.TransactionId);
            SetRequestParameter("out_refund_no", param.OutRefundNo);
            SetRequestParameter("refund_id", param.RefundId);
            SetRequestParameter("input_charset", "UTF-8");

            StreamReader sr = null;
            HttpWebResponse webResponse = null;
            HttpWebRequest webRequest = null;
            var resContent = "";
            var resCode = 0;

            try
            {
                webRequest = (HttpWebRequest)WebRequest.Create(GetRequestUrl());

                ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;
               webRequest.ClientCertificates.Add(new X509Certificate2(_certfile, _certfilepwd));

                webRequest.Timeout = 1 * 60 * 1000;

                webResponse = (HttpWebResponse)webRequest.GetResponse();
                sr = new StreamReader(webResponse.GetResponseStream(), Encoding.UTF8);

                resContent = sr.ReadToEnd();
                sr.Close();
                webResponse.Close();
            }
            catch (Exception exp)
            {
                if (webResponse != null)
                {
                    resCode = Convert.ToInt32(webResponse.StatusCode);
                }

                return 2;
            }

            resCode = Convert.ToInt32(webResponse.StatusCode);

            SetAllResponseParameters(resContent);

            if (VerifySign() && GetResponseParameter("retcode") == "0")
            {
                result.RetCode = GetResponseParameter("retcode").ToInt32OrDefault();
                result.RetMsg = GetResponseParameter("retmsg");
                result.Partner = GetResponseParameter("partner");
                result.TransactionId = GetResponseParameter("transaction_id");
                result.OutTradeNo = GetResponseParameter("out_trade_no");
                result.TransactionId = GetResponseParameter("out_refund_no");
                result.RefundCount = GetResponseParameter("refund_count");

                var refundOrderResults = new List<RefundOrderResult>();
                for (int i = 0; i < Int32.Parse(result.RefundCount); i++)
                {
                    var refundOrderResult = new RefundOrderResult
                    {
                        OutRefundNo = GetResponseParameter("out_refund_no_" + i),
                        RefundId = GetResponseParameter("refund_id_" + i),
                        RefundChannel = GetResponseParameter("refund_channel_" + i),
                        RefundFee = GetResponseParameter("refund_fee_") + i,
                        RefundState = GetResponseParameter("refund_state_" + i),
                        RecvUserId = GetResponseParameter("recv_user_id_") + i,
                        RecvUserName = GetResponseParameter("reccv_user_name_")
                    };
                    refundOrderResults.Add(refundOrderResult);
                }
                result.RefundOrderResults = refundOrderResults;

                return 1;
            }

            return 2;
        }
    }
}